function $m(sizeX,sizeY,data)
{
  return new Matrix(sizeX,sizeY,data);
}

function Matrix(sizeX,sizeY,data)
{
  if(sizeX == null || sizeY == null)
  {
    this.data = Array();
    this.sizeX = 0;
    this.sizeY = 0;  
  } else 
  {
    if(sizeX < 1 || sizeY < 1)
      throw new Error('Invalid Matrix Size');
    
    this.sizeX = sizeX;
    this.sizeY = sizeY;
    
    if(data == null)    
      this.empty();
    else
      this.load(data);
  }
  
}

Matrix.prototype.empty = function ()
{
  this.data = Array();
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    this.data.push(0);
}

Matrix.prototype.load = function (data)
{
  if(data.length != this.sizeY)
    throw new Error('Data Y size invalid');
         
  this.data = Array();
  
  for(var i=0;i<this.sizeY;i++)
    if(data[i].length != this.sizeX)
      throw new Error('Data X size Invalid');
    else
      for(var j=0;j<this.sizeX;j++)
        this.data.push(data[i][j]);
  
}

Matrix.prototype.get = function (x,y)
{
  return this.data[x + (y * this.sizeX)];
}

Matrix.prototype.set = function (x,y,value)
{
  this.data[x + (y * this.sizeX)] = value;
}

Matrix.prototype.transpose = function ()
{
  var temp = this.sizeX;
  this.sizeX = this.sizeY;
  this.sizeY = temp;
}

Matrix.prototype.duplicate = function (m,x,y)
{
  if(!instanceOf(m,Matrix))
    throw new Error('Bad matrix argument');

  var newM = m.clone();
  newM.sizeX = x;
  newM.sizeY = y;
  newM.data = Array();
  return newM;  
}

Matrix.prototype.add = function (m)
{
  var newM = this.duplicate(m,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(this.data[i] + m.data[i]);

  return newM;
}

Matrix.prototype.sub = function (m)
{
  var newM = this.duplicate(m,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(this.data[i] - m.data[i]);
  
  return newM;
}

Matrix.prototype.min = function (m)
{
  var newM = this.duplicate(m,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(Math.min(this.data[i],m.data[i]));
  
  return newM;
}

Matrix.prototype.max = function (m)
{
  var newM = this.duplicate(m,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(Math.max(this.data[i],m.data[i]));
  
  return newM;
}

Matrix.prototype.mul = function (m)
{
  var newM = this.duplicate(m,this.sizeX,this.sizeY);
  for(var y=0;y<newM.sizeY;y++)
    for(var x=0;x<newM.sizeX;x++)
    {
      var total = 0;
      for(var i=0;i<this.sizeY;i++)
          total += this.get(i,y) * m.get(x,i);
          
      newM.data.push(total);
    }
    
  return newM;
}

Matrix.prototype.scale = function (val)
{
  var newM = this.duplicate(this,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(this.data[i] * val);
  return newM;
}

Matrix.prototype.floor = function (v)
{
  if(v == null)
    v = 0;
    
  var newM = this.duplicate(this,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(floor(this.data[i] + v));
    
  return newM;
}

Matrix.prototype.abs = function ()
{    
  var newM = this.duplicate(this,this.sizeX,this.sizeY);
  for(var i=0;i<this.sizeX * this.sizeY;i++)
    newM.data.push(Math.abs(this.data[i]));
    
  return newM;
}

Matrix.prototype.clone = function ()
{
  var m = new Matrix();
  m.sizeX = this.sizeX;
  m.sizeY = this.sizeY;
  m.data = this.data.splice(0);
  return m;
}

Matrix.prototype.toString = function ()
{
  var string = '';
  for(var y=0;y<this.sizeY;y++)
  {   
    string += '[';
    for(var x=0;x<this.sizeX;x++)
    {
      string += this.get(x,y);
      
      if(x != this.sizeX - 1)
        string += ', ';
    }
    string += ']\n';
  }     
  
  return string;
}

function $p(x,y)
{
  return new Vector2(x,y);
}

function Vector2(x,y)
{
  Matrix.call(this,2,1,[[x,y]]);
}

Vector2.inherit(Matrix);

Vector2.prototype.__defineGetter__('x',function () {return this.get(0,0);});
Vector2.prototype.__defineSetter__('x',function (val) {this.set(0,0,val);});
Vector2.prototype.__defineGetter__('y',function () {return this.get(1,0);});
Vector2.prototype.__defineSetter__('y',function (val) {this.set(1,0,val);});

Vector2.prototype.clone = function ()
{
  return new Vector2(this.x,this.y);
}

function $v(x,y,z,w)
{
  return new Vector4(x,y,z,w);
}

function Vector4(x,y,z,w)
{    
  if(w == null)
    w = 1;
    
  Matrix.call(this,4,1,[[x,y,z,w]]);
}

Vector4.inherit(Matrix);

Vector4.prototype.__defineGetter__('x',function () {return this.get(0,0);});
Vector4.prototype.__defineSetter__('x',function (val) {this.set(0,0,val);});
Vector4.prototype.__defineGetter__('y',function () {return this.get(1,0);});
Vector4.prototype.__defineSetter__('y',function (val) {this.set(1,0,val);});
Vector4.prototype.__defineGetter__('z',function () {return this.get(2,0);});
Vector4.prototype.__defineSetter__('z',function (val) {this.set(2,0,val);});
Vector4.prototype.__defineGetter__('w',function () {return this.get(3,0);});
Vector4.prototype.__defineSetter__('w',function (val) {this.set(3,0,val);});

Vector4.prototype.clone = function ()
{
  return new Vector4(this.x,this.y,this.z,this.w);
}

function $c(r,g,b,a)
{
  return new Colour(r,g,b,a);
}

function Colour(r,g,b,a)
{
  if(a == null)
    a = 255;
    
  Matrix.call(this,4,1,[[r,g,b,a]]);
}

Colour.inherit(Matrix);

Colour.prototype.__defineGetter__('r',function () {return this.get(0,0);});
Colour.prototype.__defineSetter__('r',function (val) {this.set(0,0,val);});
Colour.prototype.__defineGetter__('g',function () {return this.get(1,0);});
Colour.prototype.__defineSetter__('g',function (val) {this.set(1,0,val);});
Colour.prototype.__defineGetter__('b',function () {return this.get(2,0);});
Colour.prototype.__defineSetter__('b',function (val) {this.set(2,0,val);});
Colour.prototype.__defineGetter__('a',function () {return this.get(3,0);});
Colour.prototype.__defineSetter__('a',function (val) {this.set(3,0,val);});

Colour.prototype.clone = function ()
{
  return new Colour(this.x,this.y,this.z,this.w);
}
